package com.csthink.common.infrastructure.util;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author <a href="mailto:security.2009@live.cn">Mars</a>
 * @since 2023-09-09
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class Jackson2Util {

    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final ObjectMapper OBJECT_MAPPER_IGNORE_BLANK = new ObjectMapper();
    private static final ObjectMapper OBJECT_MAPPER_STRICT = new ObjectMapper();

    static {
        DeserializationConfig dc = OBJECT_MAPPER.getDeserializationConfig();
        OBJECT_MAPPER.setConfig(dc.with(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
        OBJECT_MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        OBJECT_MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        OBJECT_MAPPER.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        OBJECT_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);

        DeserializationConfig dc1 = OBJECT_MAPPER_IGNORE_BLANK.getDeserializationConfig();
        OBJECT_MAPPER_IGNORE_BLANK.setConfig(dc1.with(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
        OBJECT_MAPPER_IGNORE_BLANK.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        OBJECT_MAPPER_IGNORE_BLANK.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        OBJECT_MAPPER_IGNORE_BLANK.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        OBJECT_MAPPER_IGNORE_BLANK.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        OBJECT_MAPPER_IGNORE_BLANK.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        OBJECT_MAPPER_IGNORE_BLANK.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        OBJECT_MAPPER_IGNORE_BLANK.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        OBJECT_MAPPER_IGNORE_BLANK.setTimeZone(TimeZone.getDefault());
        OBJECT_MAPPER_IGNORE_BLANK.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);
    }

    public static String toJson(Object bean) {
        if (bean == null) {
            return StringUtils.EMPTY;
        }

        try {
            return OBJECT_MAPPER.writeValueAsString(bean);
        } catch (JsonProcessingException e) {
            log.error("toJson error", e);
            throw new RuntimeException(e);
        }
    }

    public static String tryToJson(Object bean) {
        if (bean == null) {
            return StringUtils.EMPTY;
        }

        try {
            return OBJECT_MAPPER.writeValueAsString(bean);
        } catch (JsonProcessingException e) {
            log.error("tryToJson error", e);
            return StringUtils.EMPTY;
        }
    }

    public static String toJsonIgnoreBlank(Object bean) {
        if (bean == null) {
            return StringUtils.EMPTY;
        }

        try {
            return OBJECT_MAPPER_IGNORE_BLANK.writeValueAsString(bean);
        } catch (JsonProcessingException e) {
            log.error("toJsonIgnoreBlank error", e);
            throw new RuntimeException(e);
        }
    }

    public static String tryToJsonIgnoreBlank(Object bean) {
        if (bean == null) {
            return StringUtils.EMPTY;
        }

        try {
            return OBJECT_MAPPER_IGNORE_BLANK.writeValueAsString(bean);
        } catch (JsonProcessingException e) {
            log.error("tryToJsonIgnoreBlank error", e);
            return StringUtils.EMPTY;
        }
    }

    public static <T> T toBean(Map<String, Object> map, Class<T> clazz) {
        if (map == null || map.size() == 0) {
            return null;
        }

        try {
            return OBJECT_MAPPER.convertValue(map, clazz);
        } catch (Exception e) {
            log.error("toBean error", e);
            throw new RuntimeException(e);
        }
    }

    public static <T> T toBean(InputStream inputStream, Class<T> clazz) {
        if (inputStream == null) {
            return null;
        }

        try {
            return OBJECT_MAPPER.readValue(inputStream, clazz);
        } catch (Exception e) {
            log.error("toBean error", e);
            throw new RuntimeException(e);
        }
    }

    public static <T> T toBean(String json, Class<T> clazz) {
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (Exception e) {
            log.error("toBean error", e);
            throw new RuntimeException(e);
        }
    }

    public static <T> T tryToBean(String json, Class<T> clazz) {
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (Exception e) {
            log.error("toBean error", e);
            return null;
        }
    }

    public static <T> T tryToBeanStrict(String json, Class<T> clazz) {
        if (StringUtils.isBlank(json)) {
            return null;
        }

        try {
            return OBJECT_MAPPER_STRICT.readValue(json, clazz);
        } catch (Exception e) {
            log.error("toBean error", e);
            return null;
        }
    }

    public static <T> List<T> toList(String json, Class<? super T> clazz) {
        if (StringUtils.isBlank(json)) {
            return new ArrayList<>(0);
        }

        try {
            JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);
            return OBJECT_MAPPER.readValue(json, javaType);
        } catch (Exception e) {
            log.error("toList error", e);
            throw new RuntimeException(e);
        }
    }

    public static <T> List<T> toList(List<Map<String, Object>> list, Class<T> clazz) {
        if (list == null || list.size() == 0) {
            return null;
        }

        try {
            JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);
            return OBJECT_MAPPER.convertValue(list, javaType);
        } catch (Exception e) {
            log.error("toList error", e);
            throw new RuntimeException(e);
        }
    }

    public static Map<String, Object> toMap(String json) {
        return toMap(json, String.class, Object.class);
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> kClazz, Class<?>... vClazz) {
        if (StringUtils.isBlank(json)) {
            return new HashMap<>(0);
        }

        try {
            JavaType valueType;
            if (vClazz[0] == List.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, vClazz[1]);
            } else if (vClazz[0] == Set.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(Set.class, vClazz[1]);
            } else if (vClazz[0] == String.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else if (vClazz[0] == Object.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else if (vClazz[0] == Map.class) {
                return toMap(json, (Class<K>) vClazz[1], vClazz[2]);
            } else if (vClazz[0] == Integer.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else {
                throw new RuntimeException(String.format("unsupported class type, %s, %s", kClazz, vClazz));
            }
            JavaType keyType = OBJECT_MAPPER.getTypeFactory().constructType(kClazz);
            JavaType mapType = OBJECT_MAPPER.getTypeFactory().constructMapType(Map.class, keyType, valueType);
            return OBJECT_MAPPER.readValue(json, mapType);
        } catch (Exception e) {
            log.error("toMap error", e);
            throw new RuntimeException(e);
        }
    }

    public static <K, V> Map<K, V> tryToMap(String json, Class<K> kClazz, Class<?>... vClazz) {
        if (StringUtils.isBlank(json)) {
            return new HashMap<>(0);
        }

        try {
            JavaType valueType;
            if (vClazz[0] == List.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, vClazz[1]);
            } else if (vClazz[0] == Set.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(Set.class, vClazz[1]);
            } else if (vClazz[0] == String.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else if (vClazz[0] == Object.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else if (vClazz[0] == Map.class) {
                return toMap(json, (Class<K>) vClazz[1], vClazz[2]);
            } else if (vClazz[0] == Integer.class) {
                valueType = OBJECT_MAPPER.getTypeFactory().constructType(vClazz[0]);
            } else {
                throw new RuntimeException(String.format("unsupported class type, %s, %s", kClazz, vClazz));
            }
            JavaType keyType = OBJECT_MAPPER.getTypeFactory().constructType(kClazz);
            JavaType mapType = OBJECT_MAPPER.getTypeFactory().constructMapType(Map.class, keyType, valueType);
            return OBJECT_MAPPER.readValue(json, mapType);
        } catch (Exception e) {
            log.error("toMap error", e);
            return new HashMap<>(0);
        }
    }

    public static Map<String, Object> toMap(Object obj) throws IllegalArgumentException {
        return OBJECT_MAPPER.convertValue(obj, new TypeReference<Map<String, Object>>() {
        });
    }
}
